﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Controls.Classes;
using System.Drawing;
using Silvermoon.Controls.Interfaces;
using Silvermoon.Animations;

namespace Silvermoon.Controls
{
    /// <summary>
    /// Panel that contains collections of controls and provides a vertical scrollbar if the content does not fit into the available region.
    /// </summary>
    public class ScrollPanel : EventControl, ICompositeControl, IScrollEventBehavior, INotifyControlCollection
    {
        const int HideScrollbarDelayValue = 1200;
        #region fields

        private int hideScrollbarTick;
        private ControlCollection controls;
        private Panel container;
        private List<Control> subControls;
        private int topOffset;
        private int mouseTop;
        private int? totalHeight;
        private Animation moveAnimation;
        private VScrollBar vScrollBar;
        private bool showScrollBar;
        private bool scrolling;


        #endregion
        #region ctor

        public ScrollPanel()
            : base()
        {
            controls = new ControlCollection(this);
            container = new Panel();
            subControls = new List<Control>(2);
            subControls.Add(container);
        }


        public override void Dispose()
        {
            StopScrolling();
            base.Dispose();
        }

        #endregion
        #region props

        protected VScrollBar VScrollBar
        {
            get
            {
                if (vScrollBar == null) vScrollBar = new VScrollBar();
                return vScrollBar;
            }
        }

        /// <summary>
        /// Gets or sets whether to show the scrollbar.
        /// </summary>
        public virtual bool ShowScrollbar
        {
            get { return showScrollBar; }
            set
            {
                if (showScrollBar != value)
                {
                    showScrollBar = value;
                    OnShowScrollbarChanged();
                }
            }
        }

        protected internal override IList<Control> SubControls { get { return subControls; } }

        public int TotalHeight
        {
            get
            {
                if (totalHeight == null) totalHeight = GetTotalHeight();
                return totalHeight.Value;
            }
            private set
            {
                if (totalHeight != value)
                {
                    totalHeight = value;
                    Invalidate(InvalidationFlag.LayoutAppearance);
                }

            }
        }

        public int TopOffset
        {
            get { return topOffset; }
            set
            {
                if (topOffset != value)
                {
                    topOffset = value;
                    Invalidate(InvalidationFlag.LayoutAppearance);
                }
            }
        }

        #endregion
        #region methods

        protected override void OnLayout()
        {
            if (CheckFlag(InvalidationFlag.Size | InvalidationFlag.LayoutAppearance))
            {
                container.Bounds = new Rectangle(0, -topOffset, Width, TotalHeight);
                AttachSubControls();

                if (vScrollBar != null && vScrollBar.Visible)
                {
                    VScrollBar.Bounds = GetVScrollbarBounds();
                    VScrollBar.Maximum = TotalHeight;
                    VScrollBar.LargeChange = Height;
                    VScrollBar.Value = TopOffset;
                }
            }
            base.OnLayout();
        }

        protected virtual Rectangle GetVScrollbarBounds()
        {
            return new Rectangle(Width - VScrollBar.Width, 0, VScrollBar.Width, Height);
        }

        protected virtual void AttachSubControls()
        {
            Rectangle clip = new Rectangle(0, TopOffset, Width, Height);
            IList<Control> list = container.Controls;
            list.Clear();
            foreach (Control c in controls)
            {
                if (c.Visible && clip.IntersectsWith(c.Bounds))
                {
                    list.Add(c);
                }
            }
        }

        protected virtual int GetTotalHeight()
        {
            int total = 0;
            foreach (Control c in controls)
            {
                total = Math.Max(total, c.Bottom);
            }
            return total;
        }

        public override void OnMouseUp(MouseUpEventArgs e)
        {
            base.OnMouseUp(e);
            BounceToDefaultPosition();
            ShowScrollbar = false;
        }

        protected virtual void BounceToDefaultPosition()
        {
            Rectangle mr = new Rectangle(0, 0, Width, Height);
            if (TotalHeight > mr.Height)
            {
                int top = 0;
                int height = mr.Bottom;
                if (TopOffset < top) ScrollTo(top);
                else if ((TopOffset + height + top) > TotalHeight) ScrollTo(TotalHeight - height - top);
            }
            else
            {
                if (TopOffset != 0) ScrollTo(0);
            }
        }

        public void ScrollTo(int top)
        {
            StopScrolling();
            moveAnimation = new Animation(200, TopOffset, top, 0);
            moveAnimation.Progress += new EventHandler<AnimEventArgs>(OnScrolling);
            moveAnimation.InvokeAsync();
        }

        void OnScrolling(object sender, AnimEventArgs e)
        {
            TopOffset = e.Value;
        }

        private void StopScrolling()
        {
            ShowScrollbar = false;
            if (moveAnimation != null)
            {
                moveAnimation.Abort();
            }
        }

        protected override void OnSizeChanged()
        {
            base.OnSizeChanged();
            BounceToDefaultPosition();
            MakeFocusedControlVisible();
        }

        protected virtual void MakeFocusedControlVisible()
        {
            if (container.HasFocus)
            {
                foreach (Control c in Controls)
                {
                    if (c.VisibleAndEnabled && c.HasFocus)
                    {
                        Rectangle r = c.Bounds;
                        if (r.Top < TopOffset) TopOffset = r.Top;
                        else if (r.Bottom > (TopOffset + Height)) TopOffset = r.Bottom - Height;
                        break;
                    }
                }
            }
        }

        public override void OnLostFocus()
        {
            ShowScrollbar = false;
            base.OnLostFocus();
        }


        protected virtual void OnShowScrollbarChanged()
        {
            if (showScrollBar)
            {
                VScrollBar.Alpha = 255;
                VScrollBar.Visible = true;
                Screen.Tick -= HideScrollbarDelay;
                subControls.Add(VScrollBar);
            }
            else
            {
                hideScrollbarTick = Environment.TickCount;
                Screen.Tick += HideScrollbarDelay;
            }
            Invalidate(InvalidationFlag.LayoutAppearance);
        }

        void HideScrollbarDelay(object sender, Silvermoon.Core.TimeEventArgs e)
        {
            int dt = e.Tick - hideScrollbarTick;
            if (dt > HideScrollbarDelayValue)
            {
                dt -= HideScrollbarDelayValue;
                int alpha = 255 - dt / 4;
                if (alpha < 0) alpha = 0;
                VScrollBar.Alpha = alpha;
                if (alpha <= 0)
                {
                    Screen.Tick -= HideScrollbarDelay;
                    subControls.Remove(VScrollBar);
                    VScrollBar.Visible = false;
                }
            }
        }



        #endregion
        #region ICompositeControl Members

        public IList<Control> Controls
        {
            get { return controls; }
        }

        #endregion

        #region IScrollEventBehavior Members


        public virtual void OnScroll(ScrollEventArgs e)
        {
            if (!e.Handled && scrolling)
            {
                int dy = e.Dy;
                ShowScrollbar = true;
                TopOffset = mouseTop - dy;
                int top = TopOffset;
                if (!e.IsMouseDown)
                {
                    if (top < 0 || (top + ClientHeight) > TotalHeight) e.Cancel = true;
                }

                e.Handled = true;
            }
        }

        public virtual void OnBeginScrollData(BeginScrollEventArgs e)
        {
            if (!e.Handled)
            {
                if (e.Ay > e.Ax)
                {
                    mouseTop = TopOffset;
                    scrolling = true;
                    e.Handled = true;
                }
            }
        }

        public virtual void OnEndScrollData(EndScrollEventArgs e)
        {
            scrolling = false;
            e.Handled = true;
            BounceToDefaultPosition();
            ShowScrollbar = false;

        }

        #endregion

        #region INotifyControlCollection Members

        void INotifyControlCollection.ControlAdded(Control c)
        {
            int total = totalHeight ?? 0;
            c.SizeChanged += new EventHandler(OnControlBoundsChanged);
            c.LocationChanged += new EventHandler(OnControlBoundsChanged);
            TotalHeight = Math.Max(total, c.Bottom);
        }

        void OnControlBoundsChanged(object sender, EventArgs e)
        {
            Control c = (Control)sender;
            int total = totalHeight ?? 0;
            TotalHeight = Math.Max(total, c.Bottom);

        }

        void INotifyControlCollection.CollectionCleared()
        {
            TotalHeight = 0;
        }

        void INotifyControlCollection.ControlRemoved(Control c)
        {
            if (c.Bottom == TotalHeight)
            {
                totalHeight = null;
            }
        }

        #endregion
    }
}
